#include "stdafx.h"
#include "mapgenerator.hpp"
#include <data/map/ieditableglobalmap.hpp>
#include <data/map/ieditabletile.hpp>
#include <util/vec4.h>
#include "iostream"

#include <gsl/gsl_errno.h>
#include <gsl/gsl_spline.h>

namespace Demo {

class TileGenerator
{
public:
    static const uint SPLINE_SIZE = 4;

    TileGenerator()
    {
        _spline = gsl_spline_alloc(gsl_interp_cspline_periodic, SPLINE_SIZE);
        _accelerator = gsl_interp_accel_alloc();
    }

    ~TileGenerator()
    {
        delete _spline;
        delete _accelerator;
    }

#define hat(px, py) heightMap[(px > width ? 0 : px) + \
    (width + 1) * (py > height ? 0 : py)]

    void generateKeyPoints(uint x, uint y, uint width, uint height,
                           const std::vector<float> &heightMap)
    {
        _heights[0]  = hat(x-1, y-1);
        _heights[1]  = hat(x  , y-1);
        _heights[2]  = hat(x+1, y-1);
        _heights[3]  = hat(x+2, y-1);
        _heights[4]  = hat(x-1, y  );
        _heights[5]  = hat(x  , y  );
        _heights[6]  = hat(x+1, y  );
        _heights[7]  = hat(x+2, y  );
        _heights[8]  = hat(x-1, y+1);
        _heights[9]  = hat(x  , y+1);
        _heights[10] = hat(x+1, y+1);
        _heights[11] = hat(x+2, y+1);
        _heights[12] = hat(x-1, y+2);
        _heights[13] = hat(x  , y+2);
        _heights[14] = hat(x+1, y+2);
        _heights[15] = hat(x+2, y+2);
    }

    void calculateTilePoints(Data::IEditableTile &tile)
    {
        static const uint FREQ = Data::ITile::KEYPOINTS_FREQUENCE;
        const double STEP = 1.0 / static_cast<double>(FREQ - 1);

        for (uint xi = 0; xi < FREQ; ++xi)
            for (uint yi = 0; yi < FREQ; ++yi) {
                uint index = xi + FREQ * yi;
                float ran=0;
                ran = (float)(rand()%5)/20;

                double height = getExactHeight(STEP * xi, STEP * yi);
                _points[index].x = STEP * xi;
                _points[index].y = STEP * yi;
                if ((STEP*xi > 0) && (STEP*yi > 0))
                {
                    _points[index].z = height+3;
                }
                else
                {
                    _points[index].z = height;
                }


                tile.setKeyPoint(index, height);
            }
    }

    void calculateTileNormals(const Data::IGlobalMap &map,
                              uint x, uint y,
                              Data::IEditableTile &tile)
    {
        static const uint FREQ = Data::ITile::KEYPOINTS_FREQUENCE;
        for (uint px = 0; px < FREQ; ++px)
            for (uint py = 0; py < FREQ; ++py) {
                vec3f norm = getKeyPointNormal(map, x, y, px, py);
                tile.setKeyNormal(px + FREQ * py, norm);
            }
    }

#undef hat

private:
    inline vec3f getKeyPointNormal(const Data::IGlobalMap &map,
                                   uint x, uint y, uint px, uint py)
    {
        static const uint FREQ = Data::ITile::KEYPOINTS_FREQUENCE;
        static const double STEP = 1.0 / static_cast<double>(FREQ - 1);

        auto &tile = map.cell(x, y).tile();
        double pointHeight = tile.keyPoint(px + FREQ * py);
        vec3f a(-STEP, 0, pointHeight);
        if (px == 0) {
            if (x > 0) {
                a.z = map.cell(x - 1, y).tile().keyPoint((FREQ - 2) + FREQ * py);
            }
        } else {
            a.z = tile.keyPoint((px - 1) + FREQ * py);
        }
        vec3f b(0, -STEP, pointHeight);
        if (py == 0) {
            if (y > 0) {
                b.z = map.cell(x, y - 1).tile().keyPoint(px + FREQ * (FREQ - 2));
            }
        } else {
            b.z = tile.keyPoint(px + FREQ * (py - 1));
        }
        a.z -= pointHeight;
        b.z -= pointHeight;
        a.normalize();
        b.normalize();

        return cross(a, b);
    }

    inline double getExactHeight(double x, double y)
    {
        vec4d data;
        vec4d final;
        for (uint yidx = 0; yidx < 4; ++yidx) {
            for (uint index = 0; index < 4; ++index)
                data.set(index, _heights[yidx + 4 * index]);
            final.set(yidx, cubicSpline(data, y));
        }
        return cubicSpline(final, x);
    }

    inline double cubicSpline(const vec4d &data, double arg) const
    {
        double x[SPLINE_SIZE] = {-1.0, 0.0, 1.0, 2.0};
        gsl_spline_init(_spline, x, data, SPLINE_SIZE);
        return gsl_spline_eval(_spline, arg, _accelerator);
    }

    inline vec3f &point(uint x, uint y)
    {
        return _points[x + Data::ITile::KEYPOINTS_FREQUENCE * y];
    }

    gsl_spline *_spline;
    gsl_interp_accel *_accelerator;
    float _heights[16];
    vec3f _points[Data::ITile::KEYPOINTS_COUNT];
};

MapGenerator::MapGenerator(const MapGeneratorOptions &options)
    :_options(options)
    ,_tileGen(new TileGenerator())
    ,_roughness(0.1)
{
}

MapGenerator::~MapGenerator()
{
    delete _tileGen;
}

void MapGenerator::generate(Data::IEditableGlobalMap &map) const
{
    map.create(_options.width, _options.height);

    if (_options.landscapeMethod == MapGeneratorOptions::LandscapeAprilRandom)
    {
        generateHeightMapDeprecated(map);
    }
    else if (_options.landscapeMethod == MapGeneratorOptions::LandscapeMidpointDisplacement)
    {
        generateHeightMapWithMidpointDisplacement(map);
    }
    else if (_options.landscapeMethod == MapGeneratorOptions::LandscapeDiamondSquare)
    {
        generateHeightMapWithDiamondSquare(map);
    }
}

void MapGenerator::generateHeightMapDeprecated(Data::IEditableGlobalMap &map) const
{
    srand(static_cast<unsigned>(time(NULL)));
    std::vector<float> heightMap((_options.width+1) * (_options.height+1));
    for (size_t i = 0; i < heightMap.size(); ++i)
    {
        heightMap[i] = 0.3 * static_cast<float>(abs((rand() * 317 - 1281) % 4));
    }

    for (uint x = 0; x < _options.width; ++x)
        for (uint y = 0; y < _options.height; ++y) {
            _tileGen->generateKeyPoints(x, y,
                                        _options.width,
                                        _options.height,
                                        heightMap);
            auto &tile = map.editableCell(x, y).editableTile();
            _tileGen->calculateTilePoints(tile);
            _tileGen->calculateTileNormals(map, x, y, tile);
        }
}

void MapGenerator::generateHeightMapWithMidpointDisplacement(Data::IEditableGlobalMap &map) const
{
    srand(static_cast<unsigned>(time(NULL)));
    std::vector<float> heightMap((_options.width+1) * (_options.height+1));

    _heightMap.swap(heightMap);

    mpdis(QPoint(0,0),QPoint(_options.width+1,0),QPoint(_options.width+1,_options.height+1),QPoint(0,_options.height+1));

    for (int i=0;i!= 5;i++)
    {
        DSAAlias();
    }

    for (uint x = 0; x < _options.width; ++x)
        for (uint y = 0; y < _options.height; ++y) {
            _tileGen->generateKeyPoints(x, y,
                                        _options.width,
                                        _options.height,
                                        _heightMap);
            auto &tile = map.editableCell(x, y).editableTile();
            _tileGen->calculateTilePoints(tile);
            _tileGen->calculateTileNormals(map, x, y, tile);
        }
}

void MapGenerator::generateHeightMapWithDiamondSquare(Data::IEditableGlobalMap &map) const
{
    srand(static_cast<unsigned>(time(NULL)));
    std::vector<float> heightMap((_options.width+1) * (_options.height+1),-2);
    std::vector<DSMark> markMap((_options.width+1) * (_options.height+1));

    _heightMap.swap(heightMap);

    DSAlg();
    for (int i=0;i!= 5;i++)
    {
        DSAAlias();
    }

    for (uint x = 0; x < _options.width; ++x)
        for (uint y = 0; y < _options.height; ++y) {
            _tileGen->generateKeyPoints(x, y,
                                        _options.width,
                                        _options.height,
                                        _heightMap);
            auto &tile = map.editableCell(x, y).editableTile();
            _tileGen->calculateTilePoints(tile);
            _tileGen->calculateTileNormals(map, x, y, tile);
        }
}

void MapGenerator::mpdis(QPoint edge1,QPoint edge2,QPoint edge3,QPoint edge4)const
{
    int size = abs(edge1.x()- edge2.x());

    if (size > 1)
    {
        float rnd;
        double roughness = 0.05;

        double h1,h2,h3,h4,h_center,h_up,h_down,h_left,h_right;
        QPoint center_pos,up_pos,down_pos,left_pos,right_pos;
        center_pos.setX((edge1.x() + edge2.x())/2);
        center_pos.setY((edge1.y() + edge3.y())/2);
        up_pos.setX((edge1.x() + edge2.x())/2);
        up_pos.setY(edge1.y());
        down_pos.setX((edge3.x() + edge4.x())/2);
        down_pos.setY(edge3.y());
        left_pos.setX(edge1.x());
        left_pos.setY((edge1.y() + edge4.y())/2);
        right_pos.setX(edge2.x());
        right_pos.setY((edge2.y() + edge3.y())/2);


        h1 = hat(edge1);
        h2 = hat(edge2);
        h3 = hat(edge3);
        h4 = hat(edge4);

        rnd = (float)(rand()%5);
        h_center =  (h1+h2+h3+h4)/4 + rnd*roughness*size;
        rnd = (float)(rand()%5);
        h_up = (h1+h2)/2 + rnd*roughness*size;
        rnd = (float)(rand()%5);
        h_down = (h3+h4)/2 + rnd*roughness*size;
        rnd = (float)(rand()%5);
        h_left = (h1+h4)/2 + rnd*roughness*size;
        rnd = (float)(rand()%5);
        h_right = (h2+h3)/2 + rnd*roughness*size;

        shat(center_pos,h_center);
        shat(up_pos,h_up);
        shat(down_pos,h_down);
        shat(left_pos,h_left);
        shat(right_pos,h_right);

        mpdis(edge1,QPoint(center_pos.x(),edge1.y()),center_pos,QPoint(edge1.x(),center_pos.y()));
        mpdis(QPoint(center_pos.x(),edge1.y()),edge2,QPoint(edge2.x(),center_pos.y()),center_pos);
        mpdis(center_pos,QPoint(edge3.x(),center_pos.y()),edge3,QPoint(center_pos.x(),edge3.y()));
        mpdis(QPoint(edge4.x(),center_pos.y()),center_pos,QPoint(center_pos.x(),edge4.y()),edge4);
     }
}

float MapGenerator::hat(QPoint point)const
{
    uint _x = point.x();
    uint _y = point.y();
    if (point.x() > _options.width)
    {
        _x = _options.width;
    }
    if (point.y() > _options.height)
    {
        _y = _options.height;
    }

    if (point.x() < 0)
    {
        _x = 0;
    }
    if (point.y() < 0)
    {
        _y = 0;
    }

    return _heightMap[_x + (_options.width + 1) * _y];
}

void MapGenerator::shat(QPoint point,float h)const
{
    int _x = point.x();
    int _y = point.y();
    if (point.x() > _options.width)
    {
        _x = _options.width;
    }
    if (point.y() > _options.height)
    {
        _y = _options.height;
    }
    if (point.x() < 0)
    {
        _x = 0;
    }
    if (point.y() < 0)
    {
        _y = 0;
    }
    _heightMap[_x + (_options.width + 1) * _y] = h;
}

void MapGenerator::DSAlg()const
{
    for (int size = _options.width;size > 1;size = size/2)
    {
        int step_num = _options.width/size;
        for (int i=0;i!= step_num;i++)
        {
            for (int j=0;j!= step_num;j++)
            {
                squareStep(QPoint(i*size,j*size),
                           QPoint((i+1)*size,j*size),
                           QPoint((i+1)*size,(j+1)*size),
                           QPoint(i*size,(j+1)*size));
            }
        }

        for (int i=0;i!= step_num;i++)
        {
            for (int j=0;j!= step_num;j++)
            {
                diamondStep(QPoint((i-0.5)*size,(j+0.5)*size),
                           QPoint(i*size,j*size),
                           QPoint((i+0.5)*size,(j+0.5)*size),
                           QPoint(i*size,(j+1)*size));
                diamondStep(QPoint(i*size,j*size),
                           QPoint((i+0.5)*size,(j-0.5)*size),
                           QPoint((i+1)*size,j*size),
                           QPoint((i+0.5)*size,(j+0.5)*size));
                diamondStep(QPoint((i+0.5)*size,(j+0.5)*size),
                           QPoint((i+1)*size,j*size),
                           QPoint((i+1.5)*size,(j+0.5)*size),
                           QPoint((i+1)*size,(j+1)*size));
                diamondStep(QPoint(i*size,(j+0.5)*size),
                           QPoint((i+0.5)*size,j*size),
                           QPoint((i+1)*size,(j+0.5)*size),
                           QPoint((i+0.5)*size,(j+1)*size));
            }
        }
    }
}

void MapGenerator::DSAAlias()const
{
    std::vector<float> smoothMap((_options.width+1) * (_options.height+1));
    for (int i=0;i!=_options.width+1;i++)
    {
        for (int j=0;j!=_options.height+1;j++)
        {
            float value =   hat(QPoint(i-1,j-1))+hat(QPoint(i,j-1))+hat(QPoint(i+1,j-1))+
                            hat(QPoint(i-1,j  ))+hat(QPoint(i,j  ))+hat(QPoint(i+1,j  ))+
                            hat(QPoint(i-1,j+1))+hat(QPoint(i,j+1))+hat(QPoint(i+1,j+1));
            value = value/9;


           smoothMap[i + (_options.width + 1) * j] = value;
        }
    }
    _heightMap.swap(smoothMap);
}

void MapGenerator::squareStep(QPoint edge1,QPoint edge2,QPoint edge3,QPoint edge4)const
{
    int size = edge2.x()-edge1.x();
    double h1,h2,h3,h4,h_center;
    QPoint center_pos;
    float rnd = (float)(rand()%4);
    center_pos.setX((edge2.x()+edge1.x())/2);
    center_pos.setY((edge3.y()+edge1.y())/2);

    h1 = hat(edge1);
    h2 = hat(edge2);
    h3 = hat(edge3);
    h4 = hat(edge4);

    h_center = (h1+h2+h3+h4)/4+rnd*_roughness*size;
    shat(center_pos,h_center);
}

void MapGenerator::diamondStep(QPoint edge1,QPoint edge2,QPoint edge3,QPoint edge4)const
{
    int size = abs(edge3.x()-edge1.x());
    double h1,h2,h3,h4,h_center;
    QPoint center_pos;
    float rnd = (float)(rand()%4);
    center_pos.setX((edge3.x()+edge1.x())/2);
    center_pos.setY((edge4.y()+edge2.y())/2);

    h1 = hat(edge1);
    h2 = hat(edge2);
    h3 = hat(edge3);
    h4 = hat(edge4);

    h_center = (h1+h2+h3+h4)/4+rnd*_roughness*size;
    shat(center_pos,h_center);

}

} // namespace Demo
